﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using OMap.Tools;


namespace OMap {

	/// <summary>
	/// Implements the logic for mapping property values from one object into another object.
	/// </summary>
    public sealed class ElementMapper<TSource,TTarget> : ElementMapper,IObjectMapper<TSource,TTarget> {


		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		/// <param name="oMap">The root mapping object.</param>
        public ElementMapper(bool overwriteIfNotDefault,OMap oMap)
            : base(typeof(TSource), typeof(TTarget), overwriteIfNotDefault,oMap) {
            
        }


		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		public ElementMapper(bool overwriteIfNotDefault)
			: base(typeof(TSource), typeof(TTarget), overwriteIfNotDefault) {

		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		/// <param name="createTarget">The logic that should be executed to create a new instance.</param>
		public ElementMapper(bool overwriteIfNotDefault, Func<IObjectMapper, object> createTarget)
			: base(typeof(TSource), typeof(TTarget), overwriteIfNotDefault, createTarget) {
		}



		/// <summary>
		/// Maps from the passed source object into a new target object.
		/// </summary>
		/// <param name="source">The source object that should be mapped into a new target object.</param>
		/// <param name="referenceDict">The reference dict.</param>
		/// <returns></returns>
		public new TTarget MapTo(TSource source, Dictionary<object, object> referenceDict) {          
			 return (TTarget)base.MapTo(source,referenceDict);
        }


		/// <summary>
		/// Maps from the passed source object into an existing target object.
		/// </summary>
		/// <param name="source">The source object that should be mapped into a new target object.</param>
		/// <param name="target">The target object that should be used as mapping target.</param>
		/// <param name="referenceDict">The reference dict.</param>
		/// <returns></returns>
		public new TTarget MapTo(TSource source, TTarget target, Dictionary<object, object> referenceDict) {
			return (TTarget)base.MapTo(source, target,referenceDict);
		}

    }

	/// <summary>
	/// Implements the logic for mapping property values from one object into another object.
	/// </summary>
    public class ElementMapper : BaseMapper {
		#region Private Fields
        private IEnumerable<PropertyMatch> _propertyMatches;    
		#endregion

		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="sourceType">Type of the source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		/// <param name="oMap">The oMap Object.</param>
        public ElementMapper(Type sourceType,Type targetType, bool overwriteIfNotDefault,OMap oMap):this(sourceType,targetType,overwriteIfNotDefault,oMap,
			_defaultCreateTarget) {           
        }

		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="sourceType">Type of the source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		public ElementMapper(Type sourceType, Type targetType, bool overwriteIfNotDefault)
			: this(sourceType, targetType, overwriteIfNotDefault, null,
				_defaultCreateTarget) {
		}


		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="sourceType">Type of the source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		/// <param name="oMap">The oMap Object.</param>
		/// <param name="createTarget">The logic that should be executed to create a new instance.</param>
		public ElementMapper(Type sourceType, Type targetType, bool overwriteIfNotDefault, OMap oMap,Func<IObjectMapper,object> createTarget):base(sourceType,targetType,overwriteIfNotDefault,oMap,createTarget) {
		}


		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="sourceType">Type of the source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		/// <param name="createTarget">The logic that should be executed to create a new instance.</param>
		public ElementMapper(Type sourceType, Type targetType, bool overwriteIfNotDefault, Func<IObjectMapper, object> createTarget)
			: this(sourceType, targetType, overwriteIfNotDefault,null, createTarget) {
		}


		/// <summary>
		/// Maps from the passed source object into a new target object.
		/// </summary>
		/// <param name="source">The source object that should be mapped into a new target object.</param>
		/// <param name="referenceDict">The reference dict.</param>
		/// <returns></returns>
		public override object MapTo(object source, Dictionary<object, object> referenceDict) {          
            return MapTo(source, null,referenceDict);
        }


		/// <summary>
		/// Maps from the passed source object into an existing target object.
		/// </summary>
		/// <param name="source">The source object that should be mapped into a new target object.</param>
		/// <param name="target">The target object that should be used as mapping target.</param>
		/// <param name="referenceDict">The reference dict.</param>
		/// <returns></returns>
		public override object MapTo(object source, object target, Dictionary<object, object> referenceDict) {
			_mappedSourceValue = source;

			//if sourcevalue equals target value or
			//if source value has the default value no mapping has to be done
			//and the targetvalue will be set to the sourcevalue
			if (source == target || TypeHelper.HasDefaultValue(source, SourceType)) {
				_mappedTargetValue = source;
				return source;
			}

			_mappedTargetValue = target;
			//Create a new object for the target object if the target is equal to it's default value
			//null or a specific value for value types
			if (TypeHelper.HasDefaultValue(target, TargetType) || CreateTarget != _defaultCreateTarget ) {
				target = CreateTarget(this);
				_mappedTargetValue = target;
			}
			

			//update reference dict
			if (!_targetType.IsValueType) {
				referenceDict[source] = target;
			}

			foreach (var propertyMatch in _propertyMatches) {
				object sourcePropertyVal = null;
				object targetPropertyVal = null;
				
				try {
					sourcePropertyVal = _sourcePropertyAccessor.GetPropertyValue(source, propertyMatch.SourceProperty.Name);
				}catch(Exception exception) {
					Debug.Write(string.Format("Error occured while getting value for source property{0}:{1}",propertyMatch.SourceProperty.Name,exception.ToString()));
				}

				
				try {
					targetPropertyVal = _destinationPropertyAccessor.GetPropertyValue(target,
					                                                              propertyMatch.DestinationProperty.Name);
				}catch(Exception exception) {
					Debug.Write(string.Format("Error occured while getting value for source property{0}:{1}",propertyMatch.DestinationProperty.Name,exception.ToString()));
				}

				//if target value has a none default value and the overwrite target was not set to true
                //the target property wont be set.
                if(!TypeHelper.HasDefaultValue(targetPropertyVal,propertyMatch.DestinationProperty.PropertyType) && !OverwriteTarget ) {
                    continue;
                }
                
                //if the objectmapper is null a simple type will be mapped
                if (propertyMatch.ObjectMapper == null) {
					try {
						//Copy value from source property into target property
						_destinationPropertyAccessor.SetPropertyValue(target, propertyMatch.DestinationProperty.Name,
																	  sourcePropertyVal);
					} catch (Exception exception) {
						Debug.Write(string.Format("Error occured while setting value of destination property{0}:{1}", propertyMatch.DestinationProperty.Name, exception.ToString()));
					}

                    
                }else {
                    //if sourcevalue equals target value or
                    //if source value has the default value no mapping has to be done
                    //and the targetvalue will be set to the sourcevalue
                    if(sourcePropertyVal == targetPropertyVal || TypeHelper.HasDefaultValue(sourcePropertyVal,propertyMatch.SourceProperty.PropertyType)) {
                        targetPropertyVal = sourcePropertyVal;
                    }
                    //map a complex type
                    //to resolve circular references try to get value from reference dict.
					if (sourcePropertyVal != null && referenceDict.ContainsKey(sourcePropertyVal)) {
						targetPropertyVal = referenceDict[sourcePropertyVal];
                    }else {
						////////////////////////////////
						//use mapper of current property
						////////////////////////////////
                        //if targetVal is null a new instance must be created
						propertyMatch.ObjectMapper.CalledBy = this;

						//Resolve a more specialized mapper if subtypes are available
						if(sourcePropertyVal != null &&
 						  !(propertyMatch.ObjectMapper is ICollectionMapper) &&
						  !sourcePropertyVal.GetType().IsAssignableFrom(propertyMatch.ObjectMapper.SourceType) &&
						   sourcePropertyVal.GetType().IsSubclassOf(propertyMatch.ObjectMapper.SourceType))
						{
							var mapper = OMap.GetMapper(sourcePropertyVal.GetType(), null, propertyMatch.ObjectMapper.OverwriteTarget,false);
							if (mapper != null)
							{
								propertyMatch.ObjectMapper = mapper;
							}
						}

                        if(targetPropertyVal == null) {
                            targetPropertyVal = propertyMatch.ObjectMapper.MapTo(sourcePropertyVal,referenceDict);
                        }else {
                            targetPropertyVal = propertyMatch.ObjectMapper.MapTo(sourcePropertyVal, targetPropertyVal,referenceDict);
                        }
                        
                    }
					try {
						_destinationPropertyAccessor.SetPropertyValue(target, propertyMatch.DestinationProperty.Name,
																  targetPropertyVal);
					} catch (Exception exception) {
						Debug.Write(string.Format("Error occured while setting value of destination property{0}:{1}", propertyMatch.DestinationProperty.Name, exception.ToString()));
					}

                    
                }
            }

            return target;
        }

		

		
		/// <summary>
		/// Creates the target object.
		/// </summary>
		private static object _defaultCreateTarget(IObjectMapper mapper){		
			return TypeHelper.CreateDefaultInstance(mapper.TargetType);			
		}

		/// <summary>
		/// Initializes the specified Root Mapping Object <see cref="OMap"/>.
		/// </summary>
		/// <param name="oMap">The root mapping object.</param>
		public override sealed void Initialize(OMap oMap) {
			if (oMap != null)
			{
				this._oMap = oMap;
				this._propertyMatches = PropertyMatch.CreateMatches(this.SourceType, this.TargetType, this.OMap,
				                                                    this.OverwriteTarget);
				this.IsInitialized = true;
			}
			
		}
	}
}
